package org.xx.armory.netty;

import io.netty.buffer.ByteBufInputStream;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.util.AsciiString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.rpc.RpcCookie;
import org.xx.armory.rpc.RpcRequest;
import org.xx.armory.rpc.RpcVerb;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.stream.Collectors;

import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
import static io.netty.handler.codec.http.HttpHeaderNames.IF_MODIFIED_SINCE;
import static io.netty.handler.codec.http.HttpMethod.DELETE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.HEAD;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpMethod.PUT;
import static io.netty.handler.codec.http.HttpUtil.getCharset;
import static io.netty.handler.codec.http.HttpUtil.getMimeType;
import static io.netty.util.ReferenceCountUtil.release;
import static java.net.InetAddress.getByName;
import static java.net.URLDecoder.decode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notNull;
import static org.xx.armory.http.HttpUtils.parseHttpDate;

/**
 * 用于channel的RPC请求实现。
 *
 * @author Haart
 */
final class ChannelRpcRequest
        implements RpcRequest {
    private static final AsciiString COOKIE = AsciiString.of("Cookie");
    private final Logger logger = LoggerFactory.getLogger(ChannelRpcRequest.class);
    private FullHttpRequest request;

    /**
     * 构造{@code ChannelRpcRequest}对象的实例。
     *
     * @param request
     *         HTTP请求对象。
     * @throws IllegalArgumentException
     *         如果参数{@code request}是{@code null}。
     */
    ChannelRpcRequest(
            FullHttpRequest request
    ) {
        this.request = notNull(request, "request").retain();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final InetAddress getRemoteHost() {
        String remoteAddress = this.request.headers().get(HOST);
        if (isBlank(remoteAddress)) {
            remoteAddress = this.request.headers().get("X-Real-IP");
        }
        if (!isBlank(remoteAddress)) {
            try {
                return getByName(remoteAddress);
            } catch (UnknownHostException ex) {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final RpcVerb getVerb() {
        final HttpMethod method = this.request.method();
        if (GET.equals(method)) {
            return RpcVerb.GET;
        } else if (POST.equals(method)) {
            return RpcVerb.POST;
        } else if (PUT.equals(method)) {
            return RpcVerb.PUT;
        } else if (DELETE.equals(method)) {
            return RpcVerb.DELETE;
        } else if (HEAD.equals(method)) {
            return RpcVerb.HEAD;
        } else {
            return RpcVerb.UNKNOWN;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getContentType() {
        final CharSequence contentType = getMimeType(request);
        return contentType != null ? contentType.toString() : "application/x-www-form-urlencoded";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getAccept() {
        return this.request.headers().get(ACCEPT);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date getIfModifiedSince() {
        try {
            return parseHttpDate(this.request.headers().get(IF_MODIFIED_SINCE));
        } catch (ParseException ex) {
            logger.warn("cannot parse header if-modified-since");
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getPath() {
        final String uri = trimToEmpty(this.request.uri());
        int questIndex = uri.indexOf('?');
        String encodedPath = questIndex != -1 ? substring(uri, 0, questIndex).trim() : uri;

        try {
            return decode(encodedPath, UTF_8.name());
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalStateException("cannot decode content to " + UTF_8, ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getQueryString() {
        final String uri = trimToEmpty(request.uri());
        int questIndex = uri.indexOf('?');
        return questIndex != -1 ? substring(uri, questIndex + 1).trim() : "";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<RpcCookie> getCookies() {
        final String cookieString = this.request.headers().get(COOKIE, "");
        return ServerCookieDecoder.STRICT.decode(cookieString.trim())
                                         .stream()
                                         .filter(Objects::nonNull)
                                         .map(this::toRpcCookie)
                                         .collect(Collectors.toSet());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getBody() {
        final Charset charset = getCharset(request, UTF_8);
        return this.request.content().duplicate().toString(charset);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InputStream getInputStream() {
        return new ByteBufInputStream(this.request.content().duplicate());
    }

    /**
     * 将netty中的Cookie对象转化为RpcCookie对象。
     *
     * @param cookie
     *         待转化的netty中的Cookie对象。
     * @return RpcCookie对象。
     * @throws IllegalArgumentException
     *         如果参数{@code cookie}是{@code null}。
     */
    private RpcCookie toRpcCookie(
            Cookie cookie
    ) {
        notNull(cookie, "cookie");
        return new RpcCookie(cookie.name(), cookie.value(), cookie.maxAge(), cookie.domain(), cookie.path());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        return "(" + getVerb() + ") " + this.request.uri();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void close()
            throws Exception {
        release(this.request);
    }
}
